package main

import (
	"net"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/pushdotccgzs/coapmsg"
)

type PushMessages struct {
	dbid uint
	data []byte
}

type MessageState struct {
	lock *sync.Mutex
	data map[string]uint
}

var glmessagestate MessageState

type CoAPServer interface {
	BindAndListen() (err error)
	ServerForEver()
	IncomingUDPData(data []byte, addr net.UDPAddr)
}

type Server struct {
	Port uint16
	conn *net.UDPConn
}

func (s *Server) BindAndListen() (err error) {
	localAddr := &net.UDPAddr{
		IP:   net.ParseIP("0.0.0.0"),
		Port: int(s.Port),
	}
	conn, err := net.ListenUDP(protocol, localAddr)
	if err != nil {
		logger.Errorf("listen on CoAP %s, port %d failed.", protocol, s.Port)
		return
	}
	s.conn = conn
	return nil
}

func (s *Server) ServerForEver() {
	glmessagestate.Init()
	buffer := make([]byte, readBufferSize)
	for {
		length, remoteAddr, err := s.conn.ReadFromUDP(buffer)
		if err != nil {
			logger.Warning("read from udp failed.", err)
			continue
		}
		dstBuf := make([]byte, length)
		copy(dstBuf, buffer)
		go s.IncomingUDPData(dstBuf, *remoteAddr)
	}
}

func CoAPServerInit() {
	//CoAP server
	server = &Server{
		Port: GlobalConfig().CoapPort,
	}
	err := server.BindAndListen()
	if err != nil {
		logger.Error("CoAP bind and listen failed.")
		os.Exit(2)
		return
	}
	logger.Infof("listen CoAP port %d succ.", server.Port)
	server.ServerForEver()
}

func (m *MessageState) Init() {
	m.lock = new(sync.Mutex)
	m.data = make(map[string]uint)
}

func (m *MessageState) Contains(msghash string) (b bool) {
	m.lock.Lock()
	defer m.lock.Unlock()
	_, b = m.data[msghash]
	return
}

func (m *MessageState) Dbid(msghash string) (dbid uint) {
	m.lock.Lock()
	defer m.lock.Unlock()
	dbid = m.data[msghash]
	logger.Infof("get msgid %d to global map.", dbid)
	return
}

func (m *MessageState) Remove(msghash string) {
	m.lock.Lock()
	defer m.lock.Unlock()
	delete(m.data, msghash)
}

func (m *MessageState) Add(msghash string, db_msgid uint) {
	m.lock.Lock()
	defer m.lock.Unlock()
	m.data[msghash] = db_msgid
	logger.Infof("add msgid %d to global map.", db_msgid)
}

func (s *Server) IncomingUDPData(data []byte, addr net.UDPAddr) {
	//logger.Infof("RECV: [%s], from %s.\n", byte2Hex(data), addr.String())
	defer func() {
		if err := recover(); err != nil {
			logger.Error(err)
			logger.Error("ignore, panic occured.")
			return
		}
	}()
	msg, err := coapmsg.BytesToMessage(data)
	if err != nil {
		logger.Error("deserialize coap message failed.", err)
		return
	}
	//logger.Infof("RECV\n%s\n", ShowCoAP(msg))
	if msg.GetMessageType() == coapmsg.MessageAcknowledgment {
		logger.Info("RECV ACK.")
		handleACK(msg, s.conn, addr)
		return
	}

	if msg.GetMessageType() == coapmsg.MessageNonConfirmable {
		handleNON(msg, s.conn, addr)
		return
	}

	if msg.GetMessageType() == coapmsg.MessageConfirmable {
		logger.Info("RECV CON, register")
		handleCON(msg, s.conn, addr)
		return
	}
}

func handleACK(msg coapmsg.Message, sock *net.UDPConn, addr net.UDPAddr) (err error) {
	msgid := msg.GetMessageId()
	token := string(msg.GetToken())
	msghash := NewMsgHash(msgid, token)

	if !glmessagestate.Contains(msghash) {
		logger.Info("ignore ack.")
		return
	}
	msgdbid := glmessagestate.Dbid(msghash)
	logger.Info("recv push data ack.")
	glmessagestate.Remove(msghash)

	msgdbobj, err := QueryMessageFromMsgID(msgdbid)
	if err != nil {
		logger.Warningf("query device message id %d failed.", msgdbid)
		return
	}
	device, err := QueryDeviceFromID(uint(msgdbobj.DeviceID))
	if err != nil {
		logger.Warningf("query device id %d failed.", msgdbobj.DeviceID)
		return
	}
	RpcDataSendAck(device, msgdbid)
	err = DbChangeMsgStat(msgdbid, "SENT")
	if err != nil {
		logger.Warningf("change msgid %d to sent failed, %s", msgid, err)
		return
	}
	return
}

/*
设备注册
1，数据库查询，设备注册信息
2，返回ACK
3，设置会话信息
4，返回会话开始信息
*/
func handleCON(msg coapmsg.Message, sock *net.UDPConn, addr net.UDPAddr) {
	opt := msg.GetOption(coapmsg.OptionURIQuery)
	if opt == nil {
		logger.Warningf("recv coap con msg, cannot found opt uri query. [%s]", addr.String())
		return
	}
	uriQuery := opt.StringValue()
	if !strings.HasPrefix(uriQuery, devRegPrefix) {
		logger.Warningf("recv coap con msg, invalid format uri query. [%s]", uriQuery)
		return
	}

	Imei := uriQuery[len(devRegPrefix):]
	if len(Imei) == 0 {
		logger.Warningf("recv coap con msg, invalid devID [%s]", uriQuery)
		return
	}

	device, err := QueryDeviceID(Imei)
	if err != nil {
		logger.Warningf("device %s registered query failed.", Imei)
		sendRstCoAP(msg.GetMessageId(), msg.GetToken(), sock, addr)
		return
	}

	sendAckCoAP(msg.GetMessageId(), msg.GetToken(), sock, addr)
	//更新LATEST_PEER_ADDR
	DbUpdateDevAddr(int(device.ID), addr.String())
	//停100ms，因为抓到的包，ob报文都在确认报文之后
	time.Sleep(100 * time.Microsecond)

	//发送observe报文
	token := coapmsg.GenerateToken(TOKEN_LENGTH)
	logger.Infof("generated token %s", token)
	err = InsertNewCoAPSession(token, device.ID)
	if err != nil {
		logger.Warningf("new session failed. %s", Imei)
		return
	}

	err = sendObserveCoAP([]byte(token), sock, addr)
	if err != nil {
		logger.Warningf("send observe coap to peer failed. %s %s", Imei, addr.String())
		return
	}
	logger.Infof("dev %s, session %s, completed.", Imei, token)
	DbUpdateDevLastToken(int(device.ID), token)

	RpcDeviceRegister(device)
}

func sendObserveCoAP(token []byte, sock *net.UDPConn, addr net.UDPAddr) (err error) {
	obMsg := &coapmsg.CoapMessage{
		MessageType: coapmsg.MessageConfirmable,
		Code:        coapmsg.Get,
		MessageID:   coapmsg.GenerateMessageID(),
		Token:       token,
	}
	obMsg.AddOption(coapmsg.OptionObserve, 0)
	obMsg.AddOption(coapmsg.OptionURIPath, "t")
	obMsg.AddOption(coapmsg.OptionURIPath, "d")
	return sendCoAP(obMsg, sock, addr)
}

func sendAckCoAP(msgid uint16, token []byte, sock *net.UDPConn, addr net.UDPAddr) {
	ackMsg := &coapmsg.CoapMessage{
		MessageType: coapmsg.MessageAcknowledgment,
		Code:        coapmsg.CoapCodeValid,
		MessageID:   msgid,
		Token:       token,
	}
	sendCoAP(ackMsg, sock, addr)
}

func sendRstCoAP(msgid uint16, token []byte, sock *net.UDPConn, addr net.UDPAddr) {
	rstMsg := &coapmsg.CoapMessage{
		MessageType: coapmsg.MessageReset,
		Code:        coapmsg.CoapCodeUnauthorized,
		MessageID:   msgid,
		Token:       token,
	}
	sendCoAP(rstMsg, sock, addr)
}

/*
设备上传数据
1，数据库检查session token值，若错误，返回RST
2，内部回调
*/
func handleNON(msg coapmsg.Message, sock *net.UDPConn, addr net.UDPAddr) {
	token := string(msg.GetToken())
	logger.Infof("data report recv, session: %s, src addr: %s", token, addr.String())

	device, err := QueryDeviceFromToken(token)
	if err != nil {
		logger.Warning("find session failed.")
		sendRstCoAP(msg.GetMessageId(), msg.GetToken(), sock, addr)
		return
	}

	devdbid, imei := int(device.ID), device.ChipId
	RpcDataUploaded(device, msg.GetPayload().GetBytes())
	//数据库保存上传的数据，更新设备地址
	go func() {
		err := DbAddUploadMsg(msg, addr.String(), devdbid)
		if err != nil {
			logger.Warning("db save data recvd failed, ", err)
		}
		err = DbUpdateDevAddr(devdbid, addr.String())
		if err != nil {
			logger.Warning("DbUpdateDevAddr failed, ", err)
		}
		//更新最后的有效token
		err = DbUpdateDevLastToken(devdbid, token)
		if err != nil {
			logger.Warning("Db update last token failed.", err)
		}
	}()
	//检查是否需要推送数据
	datalist, err := QueryPushMessageList(uint(devdbid))
	if err != nil {
		logger.Warning("get push data list failed.", err)
		return
	}
	if len(datalist) == 0 {
		logger.Infof("device %s %d no push data found.", imei, devdbid)
		return
	}

	for _, pushdata := range datalist {
		sendPushDataCoAP(pushdata, token, sock, addr)
	}
}

func sendPushDataCoAP(data CoapMessage, token string, sock *net.UDPConn, addr net.UDPAddr) {
	msgid := coapmsg.GenerateMessageID()
	pushMsg := &coapmsg.CoapMessage{
		MessageType: coapmsg.MessageConfirmable,
		Code:        coapmsg.Post,
		MessageID:   msgid,
		Token:       []byte(token),
	}
	pushMsg.AddOption(coapmsg.OptionURIPath, "t")
	pushMsg.AddOption(coapmsg.OptionURIPath, "d")
	pushMsg.AddOption(coapmsg.OptionObserve, 0)
	pushMsg.SetStringPayload(string(data.Data))
	err := sendCoAP(pushMsg, sock, addr)
	if err != nil {
		logger.Warningf("send udp data to %s fail.", addr.String())
		return
	}
	glmessagestate.Add(NewMsgHash(msgid, token), data.ID)
}

/*
	MessageType uint8
	Code        CoapCode
	MessageID   uint16
	Payload     MessagePayload
	Token       []byte
	Options     []Option
*/
func PushCoapToAddr(data string, addr *net.UDPAddr, token string) (err error) {
	msgid := coapmsg.GenerateMessageID()
	msg := &coapmsg.CoapMessage{
		MessageType: coapmsg.MessageConfirmable,
		Code:        coapmsg.Post,
		MessageID:   msgid,
		Token:       []byte(token),
	}
	msg.AddOption(coapmsg.OptionURIPath, "t")
	msg.AddOption(coapmsg.OptionURIPath, "d")
	msg.AddOption(coapmsg.OptionObserve, 0)
	msg.SetStringPayload(string(data))
	err = sendCoAP(msg, server.conn, *addr)
	if err != nil {
		logger.Warningf("push udp data to %s fail. %s", addr.String(), err)
		return
	}
	//glmessagestate.Add(NewMsgHash(msgid, token), data.dbid)
	return
}
